/*
 *  JDayChooser.java  - A bean for choosing a day
 *  Copyright (C) 2004 Kai Toedter
 *  kai@toedter.com
 *  www.toedter.com
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package com.pkdl.datepicker;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import javax.swing.JButton;
import javax.swing.JPanel;

/**
 * JCalendar is a bean for choosing a day.
 * 
 * @author Kai Toedter
 * @version 1.2.1
 */
public class JDayChooser extends JPanel implements ActionListener, KeyListener,
		FocusListener {

	protected JButton[] days;
	protected JButton[] weeks;
	protected JButton selectedDay;
	protected JPanel weekPanel;
	protected JPanel dayPanel;
	protected int day;
	protected Color oldDayBackgroundColor;
	protected Color selectedColor;
	protected Color sundayForeground;
	protected Color weekdayForeground;
	protected Color decorationBackgroundColor;
	protected String[] dayNames = { "", "S", "M", "T", "W", "T", "F", "S" };
	protected Calendar calendar;
	protected Calendar today;
	protected Locale locale;
	protected boolean initialized;
	protected boolean weekOfYearVisible;
	protected boolean decorationBackgroundVisible = true;
	protected boolean decorationBordersVisible;
	private boolean alwaysFireDayProperty;

	/**
	 * Default JDayChooser constructor.
	 */
	public JDayChooser() {
		this(false);
	}

	/**
	 * JDayChooser constructor.
	 * 
	 * @param weekOfYearVisible
	 *            true, if the weeks of a year shall be shown
	 */
	public JDayChooser(boolean weekOfYearVisible) {
		this.setBackground(Color.blue);
		this.weekOfYearVisible = weekOfYearVisible;
		this.locale = Locale.getDefault();
		this.days = new JButton[49];
		this.selectedDay = null;
		this.calendar = Calendar.getInstance(this.locale);
		this.today = (Calendar) this.calendar.clone();

		this.setLayout(new BorderLayout());

		this.dayPanel = new JPanel();
		this.dayPanel.setLayout(new GridLayout(7, 7));

		this.sundayForeground = new Color(220, 0, 0);
		this.weekdayForeground = new Color(0, 90, 164);

		// decorationBackgroundColor = new Color(194, 211, 252);
		// decorationBackgroundColor = new Color(206, 219, 246);
		this.decorationBackgroundColor = new Color(210, 228, 238);

		for (int y = 0; y < 7; y++) {
			for (int x = 0; x < 7; x++) {
				int index = x + (7 * y);

				if (y == 0) {
					// Create a button that doesn't react on clicks or focus
					// changes.
					// Thanks to Thomas Schaefer for the focus hint :)
					this.days[index] = new JButton() {

						public void addMouseListener(MouseListener l) {
						}

						public boolean isFocusable() {
							return false;
						}
					};

					this.days[index]
							.setContentAreaFilled(this.decorationBackgroundVisible);
					this.days[index]
							.setBorderPainted(this.decorationBordersVisible);
					this.days[index]
							.setBackground(this.decorationBackgroundColor);
					this.days[index].setFont(new java.awt.Font(
							"Microsoft Sans Serif", 0, 11));
				} else {
					this.days[index] = new JButton("x");
					this.days[index].addActionListener(this);
					this.days[index].addKeyListener(this);
					this.days[index].addFocusListener(this);
					this.days[index]
							.setBorderPainted(this.decorationBordersVisible);
					this.days[index].setFont(new java.awt.Font(
							"Microsoft Sans Serif", 0, 10));
				}

				this.days[index].setMargin(new Insets(0, 0, 0, 0));
				this.days[index].setFocusPainted(false);
				this.dayPanel.add(this.days[index]);
			}
		}

		this.weekPanel = new JPanel();
		this.weekPanel.setLayout(new GridLayout(7, 1));
		this.weeks = new JButton[7];

		for (int i = 0; i < 7; i++) {
			this.weeks[i] = new JButton() {

				public void addMouseListener(MouseListener l) {
				}

				public boolean isFocusable() {
					return false;
				}
			};
			this.weeks[i].setMargin(new Insets(0, 0, 0, 0));
			this.weeks[i].setFocusPainted(false);
			this.weeks[i].setBackground(Color.white);
			this.weeks[i].setForeground(new Color(100, 100, 100));

			this.weeks[i]
					.setContentAreaFilled(this.decorationBackgroundVisible);
			this.weeks[i].setBorderPainted(this.decorationBordersVisible);

			if (i != 0) {
				this.weeks[i].setText("0" + (i + 1));
			}

			this.weekPanel.add(this.weeks[i]);
		}

		this.init();
		this.drawWeeks();

		this.setDay(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
		this.add(this.dayPanel, BorderLayout.CENTER);

		this.initialized = true;
	}

	/**
	 * JDayChooser is the ActionListener for all day buttons.
	 * 
	 * @param e
	 *            the ActionEvent
	 */
	public void actionPerformed(ActionEvent e) {
		JButton button = (JButton) e.getSource();
		String buttonText = button.getText();
		int day = new Integer(buttonText).intValue();
		this.setDay(day);
	}

	/**
	 * Draws the day names of the day columnes.
	 */
	private void drawDayNames() {
		int firstDayOfWeek = this.calendar.getFirstDayOfWeek();
		// DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale);
		// dayNames = dateFormatSymbols.getShortWeekdays();

		int day = firstDayOfWeek;

		for (int i = 0; i < 7; i++) {
			this.days[i].setText(this.dayNames[day]);

			if (day == 1) {
				this.days[i].setForeground(this.sundayForeground);
			} else {
				this.days[i].setForeground(this.weekdayForeground);
			}

			if (day < 7) {
				day++;
			} else {
				day -= 6;
			}
		}
	}

	/**
	 * Hides and shows the day buttons.
	 */
	protected void drawDays() {
		Calendar tmpCalendar = (Calendar) this.calendar.clone();
		int firstDayOfWeek = tmpCalendar.getFirstDayOfWeek();
		tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);

		int firstDay = tmpCalendar.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek;

		if (firstDay < 0) {
			firstDay += 7;
		}

		int i;

		for (i = 0; i < firstDay; i++) {
			this.days[i + 7].setVisible(false);
			this.days[i + 7].setText("");
		}

		tmpCalendar.add(Calendar.MONTH, 1);

		Date firstDayInNextMonth = tmpCalendar.getTime();
		tmpCalendar.add(Calendar.MONTH, -1);

		Date day = tmpCalendar.getTime();
		int n = 0;
		Color foregroundColor = this.getForeground();

		while (day.before(firstDayInNextMonth)) {
			this.days[i + n + 7].setText(Integer.toString(n + 1));
			this.days[i + n + 7].setVisible(true);

			if ((tmpCalendar.get(Calendar.DAY_OF_YEAR) == this.today
					.get(Calendar.DAY_OF_YEAR))
					&& (tmpCalendar.get(Calendar.YEAR) == this.today
							.get(Calendar.YEAR))) {
				this.days[i + n + 7].setForeground(this.sundayForeground);
			} else {
				this.days[i + n + 7].setForeground(foregroundColor);
			}

			if ((n + 1) == this.day) {
				this.days[i + n + 7].setBackground(this.selectedColor);
				this.selectedDay = this.days[i + n + 7];
			} else {
				this.days[i + n + 7].setBackground(this.oldDayBackgroundColor);
			}

			n++;
			tmpCalendar.add(Calendar.DATE, 1);
			day = tmpCalendar.getTime();
		}

		for (int k = n + i + 7; k < 49; k++) {
			this.days[k].setVisible(false);
			this.days[k].setText("");
		}
	}

	/**
	 * Hides and shows the week buttons.
	 */
	protected void drawWeeks() {
		Calendar tmpCalendar = (Calendar) this.calendar.clone();

		for (int i = 1; i < 7; i++) {
			tmpCalendar.set(Calendar.DAY_OF_MONTH, (i * 7) - 6);

			int week = tmpCalendar.get(Calendar.WEEK_OF_YEAR);
			String buttonText = Integer.toString(week);

			if (week < 10) {
				buttonText = "0" + buttonText;
			}

			this.weeks[i].setText(buttonText);

			if ((i == 5) || (i == 6)) {
				this.weeks[i].setVisible(this.days[i * 7].isVisible());
			}
		}
	}

	/**
	 * JDayChooser is the FocusListener for all day buttons. (Added by Thomas
	 * Schaefer)
	 * 
	 * @param e
	 *            the FocusEvent
	 */
	/*
	 * Code below commented out by Mark Brown on 24 Aug 2004. This code breaks
	 * the JDateChooser code by triggering the actionPerformed method on the
	 * next day button. This causes the date chosen to always be incremented by
	 * one day.
	 */
	public void focusGained(FocusEvent e) {
		// JButton button = (JButton) e.getSource();
		// String buttonText = button.getText();
		//
		// if ((buttonText != null) && !buttonText.equals("") &&
		// !e.isTemporary()) {
		// actionPerformed(new ActionEvent(e.getSource(), 0, null));
		// }
	}

	/**
	 * Does nothing.
	 * 
	 * @param e
	 *            the FocusEvent
	 */
	public void focusLost(FocusEvent e) {
	}

	/**
	 * Returns the selected day.
	 * 
	 * @return the day value
	 * 
	 * @see #setDay
	 */
	public int getDay() {
		return this.day;
	}

	/**
	 * Returns the day panel.
	 * 
	 * @return the day panel
	 */
	public JPanel getDayPanel() {
		return this.dayPanel;
	}

	/**
	 * Returns the color of the decoration (day names and weeks).
	 * 
	 * @return the color of the decoration (day names and weeks).
	 */
	public Color getDecorationBackgroundColor() {
		return this.decorationBackgroundColor;
	}

	/**
	 * Returns the locale.
	 * 
	 * @return the locale value
	 * 
	 * @see #setLocale
	 */
	public Locale getLocale() {
		return this.locale;
	}

	/**
	 * Returns "JDayChooser".
	 * 
	 * @return the name value
	 */
	public String getName() {
		return "JDayChooser";
	}

	/**
	 * Returns the Sunday foreground.
	 * 
	 * @return Color the Sunday foreground.
	 */
	public Color getSundayForeground() {
		return this.sundayForeground;
	}

	/**
	 * Returns the weekday foreground.
	 * 
	 * @return Color the weekday foreground.
	 */
	public Color getWeekdayForeground() {
		return this.weekdayForeground;
	}

	/**
	 * Initilizes the locale specific names for the days of the week.
	 */
	protected void init() {
		this.oldDayBackgroundColor = Color.white;
		this.selectedColor = Color.orange;

		Date date = this.calendar.getTime();
		this.calendar = Calendar.getInstance(this.locale);
		this.calendar.setTime(date);

		this.drawDayNames();
		this.drawDays();
	}

	/**
	 * Initializes both day names and weeks of the year.
	 */
	protected void initDecorations() {
		for (int x = 0; x < 7; x++) {
			this.days[x].setContentAreaFilled(this.decorationBackgroundVisible);
			this.days[x].setBorderPainted(this.decorationBordersVisible);
			this.weeks[x]
					.setContentAreaFilled(this.decorationBackgroundVisible);
			this.weeks[x].setBorderPainted(this.decorationBordersVisible);
		}
	}

	/**
	 * The decoration background is the background color of the day titles and
	 * the weeks of the year.
	 * 
	 * @return Returns true, if the decoration background is painted.
	 */
	public boolean isDecorationBackgroundVisible() {
		return this.decorationBackgroundVisible;
	}

	/**
	 * The decoration border is the button border of the day titles and the
	 * weeks of the year.
	 * 
	 * @return Returns true, if the decoration border is painted.
	 */
	public boolean isDecorationBordersVisible() {
		return this.decorationBordersVisible;
	}

	/**
	 * In some Countries it is often usefull to know in which week of the year a
	 * date is.
	 * 
	 * @return boolean true, if the weeks of the year is shown
	 */
	public boolean isWeekOfYearVisible() {
		return this.weekOfYearVisible;
	}

	/**
	 * JDayChooser is the KeyListener for all day buttons. (Added by Thomas
	 * Schaefer and modified by Austin Moore)
	 * 
	 * @param e
	 *            the KeyEvent
	 */
	public void keyPressed(KeyEvent e) {
		int offset = (e.getKeyCode() == KeyEvent.VK_UP) ? (-7) : ((e
				.getKeyCode() == KeyEvent.VK_DOWN) ? (+7)
				: ((e.getKeyCode() == KeyEvent.VK_LEFT) ? (-1) : ((e
						.getKeyCode() == KeyEvent.VK_RIGHT) ? (+1) : 0)));

		int newDay = this.getDay() + offset;

		if ((newDay >= 1)
				&& (newDay <= this.calendar.getMaximum(Calendar.DAY_OF_MONTH))) {
			this.setDay(newDay);
		}
	}

	/**
	 * Does nothing.
	 * 
	 * @param e
	 *            the KeyEvent
	 */
	public void keyReleased(KeyEvent e) {
	}

	/**
	 * Does nothing.
	 * 
	 * @param e
	 *            the KeyEvent
	 */
	public void keyTyped(KeyEvent e) {
	}

	/**
	 * this is needed for JDateChooser.
	 * 
	 * @param alwaysFire
	 *            true, if day property shall be fired every time a day is
	 *            chosen.
	 */
	public void setAlwaysFireDayProperty(boolean alwaysFire) {
		this.alwaysFireDayProperty = alwaysFire;
	}

	/**
	 * Sets a specific calendar. This is needed for correct graphical
	 * representation of the days.
	 * 
	 * @param calendar
	 *            the new calendar
	 */
	public void setCalendar(Calendar calendar) {
		this.calendar = calendar;
		this.drawDays();
	}

	/**
	 * Sets the day. This is a bound property.
	 * 
	 * @param d
	 *            the day
	 * 
	 * @see #getDay
	 */
	public void setDay(int d) {
		if (d < 1) {
			d = 1;
		}

		Calendar tmpCalendar = (Calendar) this.calendar.clone();
		tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);
		tmpCalendar.add(Calendar.MONTH, 1);
		tmpCalendar.add(Calendar.DATE, -1);

		int maxDaysInMonth = tmpCalendar.get(Calendar.DATE);

		if (d > maxDaysInMonth) {
			d = maxDaysInMonth;
		}

		int oldDay = this.day;
		this.day = d;

		if (this.selectedDay != null) {
			this.selectedDay.setBackground(this.oldDayBackgroundColor);
			this.selectedDay.repaint();
		}

		for (int i = 7; i < 49; i++) {
			if (this.days[i].getText().equals(Integer.toString(this.day))) {
				this.selectedDay = this.days[i];
				this.selectedDay.setBackground(this.selectedColor);

				break;
			}
		}

		if (this.alwaysFireDayProperty) {
			this.firePropertyChange("day", 0, this.day);
		} else {
			this.firePropertyChange("day", oldDay, this.day);
		}
	}

	/**
	 * Sets the background of days and weeks of year buttons.
	 * 
	 * @param decorationBackgroundColor
	 *            The background to set
	 */
	public void setDecorationBackgroundColor(Color decorationBackgroundColor) {
		this.decorationBackgroundColor = decorationBackgroundColor;

		if (this.days != null) {
			for (int i = 0; i < 7; i++) {
				this.days[i].setBackground(decorationBackgroundColor);
			}
		}

		if (this.weeks != null) {
			for (int i = 0; i < 7; i++) {
				this.weeks[i].setBackground(decorationBackgroundColor);
			}
		}
	}

	/**
	 * The decoration background is the background color of the day titles and
	 * the weeks of the year.
	 * 
	 * @param decorationBackgroundVisible
	 *            true, if the decoration background shall be painted.
	 */
	public void setDecorationBackgroundVisible(
			boolean decorationBackgroundVisible) {
		this.decorationBackgroundVisible = decorationBackgroundVisible;
		this.initDecorations();
	}

	/**
	 * The decoration border is the button border of the day titles and the
	 * weeks of the year.
	 * 
	 * @param decorationBordersVisible
	 *            true, if the decoration border shall be painted.
	 */
	public void setDecorationBordersVisible(boolean decorationBordersVisible) {
		this.decorationBordersVisible = decorationBordersVisible;
		this.initDecorations();
	}

	/**
	 * Enable or disable the JDayChooser.
	 * 
	 * @param enabled
	 *            The new enabled value
	 */
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);

		for (short i = 0; i < this.days.length; i++) {
			if (this.days[i] != null) {
				this.days[i].setEnabled(enabled);
			}
		}

		for (short i = 0; i < this.weeks.length; i++) {
			if (this.weeks[i] != null) {
				this.weeks[i].setEnabled(enabled);
			}
		}
	}

	/**
	 * Requests that the selected day also have the focus.
	 */
	public void setFocus() {
		if (this.selectedDay != null) {
			this.selectedDay.requestFocus();
		}
	}

	/**
	 * Sets the font property.
	 * 
	 * @param font
	 *            the new font
	 */
	public void setFont(Font font) {
		if (this.days != null) {
			for (int i = 0; i < 49; i++) {
				this.days[i].setFont(font);
			}
		}
	}

	/**
	 * Sets the foregroundColor color.
	 * 
	 * @param foreground
	 *            the new foregroundColor
	 */
	public void setForeground(Color foreground) {
		super.setForeground(foreground);

		if (this.days != null) {
			for (int i = 7; i < 49; i++) {
				this.days[i].setForeground(foreground);
			}

			this.drawDays();
		}
	}

	/**
	 * Sets the locale.
	 * 
	 * @param locale
	 *            the new locale value
	 * 
	 * @see #getLocale
	 */
	public void setLocale(Locale locale) {
		if (!this.initialized) {
			super.setLocale(locale);
		} else {
			this.locale = locale;
			super.setLocale(locale);
			this.init();
		}
	}

	/**
	 * Sets a specific month. This is needed for correct graphical
	 * representation of the days.
	 * 
	 * @param month
	 *            the new month
	 */
	public void setMonth(int month) {
		this.calendar.set(Calendar.MONTH, month);

		// Since the day does not change,
		// don't fire a day property change, even if alwaysFireDayProperty is
		// true :)
		boolean storedMode = this.alwaysFireDayProperty;
		this.alwaysFireDayProperty = false;
		this.setDay(this.day);
		this.alwaysFireDayProperty = storedMode;

		this.drawDays();
		this.drawWeeks();
	}

	/**
	 * Sets the Sunday foreground.
	 * 
	 * @param sundayForeground
	 *            The sundayForeground to set
	 */
	public void setSundayForeground(Color sundayForeground) {
		this.sundayForeground = sundayForeground;
		this.drawDayNames();
		this.drawDays();
	}

	/**
	 * Sets the weekday foreground.
	 * 
	 * @param weekdayForeground
	 *            The weekdayForeground to set
	 */
	public void setWeekdayForeground(Color weekdayForeground) {
		this.weekdayForeground = weekdayForeground;
		this.drawDayNames();
		this.drawDays();
	}

	/**
	 * In some Countries it is often usefull to know in which week of the year a
	 * date is.
	 * 
	 * @param weekOfYearVisible
	 *            true, if the weeks of the year shall be shown
	 */
	public void setWeekOfYearVisible(boolean weekOfYearVisible) {
		if (weekOfYearVisible == this.weekOfYearVisible) {
			return;
		} else if (weekOfYearVisible) {
			this.add(this.weekPanel, BorderLayout.WEST);
		} else {
			this.remove(this.weekPanel);
		}

		this.weekOfYearVisible = weekOfYearVisible;
		this.validate();
		this.dayPanel.validate();
	}

	/**
	 * Sets a specific year. This is needed for correct graphical representation
	 * of the days.
	 * 
	 * @param year
	 *            the new year
	 */
	public void setYear(int year) {
		this.calendar.set(Calendar.YEAR, year);
		this.drawDays();
		this.drawWeeks();
	}
}
